package me.chnxan.core;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.impl.CriteriaImpl;
import org.hibernate.transform.ResultTransformer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.ObjectRetrievalFailureException;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.util.Assert;

import me.chnxan.core.PropertyFilter.MatchType;
import me.chnxan.util.BeanUtil;
import me.chnxan.util.ReflectionUtil;
import me.chnxan.util.StringUtil;

/**
 * 
 * @author David Yang
 * @author XC
 */
@SuppressWarnings("unchecked")
public class HibernateCaller extends HibernateDaoSupport {

	protected Logger logger = LoggerFactory.getLogger(getClass());

	public HibernateCaller() {
	}

	public void flush() {
		getHibernateTemplate().flush();
	}

	public void clear() {
		getHibernateTemplate().clear();
	}

	/**
	 * create Query object Example: caller.createQuery(hql)
	 * caller.createQuery(hql,arg0); caller.createQuery(hql,arg0,arg1);
	 * caller.createQuery(hql,new Model[arg0,arg1,arg2])
	 * 
	 * @param values
	 *            variable parameter
	 */
	public Query createQuery(String hql, Object... values) {
		Assert.hasText(hql);
		Query query = getSession().createQuery(hql);
		for (int i = 0; i < values.length; i++) {
			query.setParameter(i, values[i]);
		}
		return query;
	}

	/**
	 * Create Criteria Model
	 * 
	 * @param criterions
	 *            variable parameter
	 */
	public Criteria createCriteria(Class entityClass, Criterion... criterions) {
		Criteria criteria = getSession().createCriteria(entityClass);
		for (Criterion c : criterions) {
			criteria.add(c);
		}
		return criteria;
	}

	/**
	 * 
	 * @see #createCriteria(Class,Criterion[])
	 */
	public Criteria createCriteria(Class entityClass, String orderBy,
			boolean isAsc, Criterion... criterions) {
		Assert.hasText(orderBy);

		Criteria criteria = createCriteria(entityClass, criterions);

		if (isAsc)
			criteria.addOrder(Order.asc(orderBy));
		else
			criteria.addOrder(Order.desc(orderBy));

		return criteria;
	}

	/**
	 * search by hql
	 * 
	 * @param values
	 *            valiable parameter
	 */
	public List find(String hql, Object... values) {
		Assert.hasText(hql);
		try {
			return getHibernateTemplate().find(hql, values);
		} catch (DataAccessException e) {
			return new ArrayList();
		}
	}

	/**
	 * search by hql in condition
	 * 
	 * @param values
	 *            valiable parameter
	 */
	public List find(String hql, String paraName, Object[] values) {
		Assert.hasText(hql);
		Query query = getSession().createQuery(hql);
		query.setParameterList(paraName, values);
		List list = query.list();
		return list;
	}

	/**
	 * search by hql Top result
	 * 
	 * @param values
	 *            valiable parameter
	 */
	public List find(String hql, int maxResult, Object... values) {
		Assert.hasText(hql);
		Query query = this.createQuery(hql, values);
		// query.setFirstResult(1);
		query.setMaxResults(maxResult);
		List list = query.list();
		return list;
	}

	/**
	 * 按属性过滤条件列表分页查找对象.
	 */
	public Page find(Class entityClass, final Page page, String hql,
			Object... values) {
		Assert.hasText(hql);

		Query countQuery = this.createQuery("select count(*) " + hql, values);
		if (page.isAutoCount()) {
			long totalCount = (Long) countQuery.uniqueResult();
			page.setTotalCount(totalCount);
		}

		if (page.isOrderBySetted()) {
			hql = hql + " order by " + page.getOrderBy();
		}
		Query query = this.createQuery(hql, values);
		query.setFirstResult(page.getFirst());
		query.setMaxResults(page.getPageSize());

		List result = query.list();
		page.setResult(result);
		return page;
	}

	/**
	 * 按属性过滤条件列表分页查找对象.
	 */
	public Page find(Class entityClass, final Page page, String chql,
			String hql, Object... values) {
		Assert.hasText(hql);

		Query countQuery = this.createQuery("select count(*) " + chql, values);
		if (page.isAutoCount()) {
			long totalCount = (Long) countQuery.uniqueResult();
			page.setTotalCount(totalCount);
		}

		if (page.isOrderBySetted()) {
			hql = hql + " order by " + page.getOrderBy();
		}
		Query query = this.createQuery(hql, values);
		query.setFirstResult(page.getFirst());
		query.setMaxResults(page.getPageSize());

		List result = query.list();
		page.setResult(result);
		return page;
	}

	/**
	 * 按属性过滤条件列表分页查找对象.
	 */
	public Page find(Class entityClass, final Page page,
			final List<PropertyFilter> filters) {
		Criterion[] criterions = buildFilterCriterions(filters);
		List<String> aliasList = getAliasList(filters);
		return find(entityClass, page, aliasList, criterions);
	}

	@SuppressWarnings("unchecked")
	public Page find(Class entityClass, final Page page,
			final List<String> aliasList, final Criterion... criterions) {
		Assert.notNull(page, "page不能为空");

		Criteria c = createCriteria(entityClass, criterions);
		for (String alias : aliasList) {
			c.createAlias(alias, alias);
		}

		if (page.isAutoCount()) {
			long totalCount = countCriteriaResult(c);
			page.setTotalCount(totalCount);
		}

		setPageParameter(c, page);
		List result = c.list();
		page.setResult(result);
		return page;
	}

	/**
	 * 执行count查询获得本次Criteria查询所能获得的对象总数.
	 */
	@SuppressWarnings("unchecked")
	protected long countCriteriaResult(final Criteria c) {
		CriteriaImpl impl = (CriteriaImpl) c;

		// 先把Projection、ResultTransformer、OrderBy取出来,清空三者后再执行Count操作
		Projection projection = impl.getProjection();
		ResultTransformer transformer = impl.getResultTransformer();

		List<CriteriaImpl.OrderEntry> orderEntries = null;
		try {
			orderEntries = (List) ReflectionUtil.getFieldValue(impl,
					"orderEntries");
			ReflectionUtil.setFieldValue(impl, "orderEntries", new ArrayList());
		} catch (Exception e) {
			logger.error("不可能抛出的异常:{}", e.getMessage());
		}

		// 执行Count查询
		long totalCount = (Long) c.setProjection(Projections.rowCount())
				.uniqueResult();

		// 将之前的Projection,ResultTransformer和OrderBy条件重新设回去
		c.setProjection(projection);

		if (projection == null) {
			c.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
		}
		if (transformer != null) {
			c.setResultTransformer(transformer);
		}
		try {
			ReflectionUtil.setFieldValue(impl, "orderEntries", orderEntries);
		} catch (Exception e) {
			logger.error("不可能抛出的异常:{}", e.getMessage());
		}

		return totalCount;
	}

	/**
	 * 设置分页参数到Criteria对象,辅助函数.
	 */
	protected Criteria setPageParameter(final Criteria c, final Page page) {
		c.setFirstResult(page.getFirst());
		c.setMaxResults(page.getPageSize());

		if (page.isOrderBySetted()) {
			String[] orderByArray = StringUtils.split(page.getOrderBy(), ',');
			String[] orderArray = StringUtils.split(page.getOrder(), ',');

			// Assert.isTrue(orderByArray.length == orderArray.length,
			// "分页多重排序参数中,排序字段与排序方向的个数不相等");
			Assert.isTrue(orderByArray.length == orderArray.length,
					"Order column must equals order by column");

			for (int i = 0; i < orderByArray.length; i++) {
				if (Page.ASC.equals(orderArray[i].trim())) {
					c.addOrder(Order.asc(orderByArray[i]));
				} else {
					c.addOrder(Order.desc(orderByArray[i]));
				}
			}
		}
		return c;
	}

	/**
	 * 按属性条件列表创建Criterion数组,辅助函数.
	 */
	protected Criterion[] buildFilterCriterions(
			final List<PropertyFilter> filters) {
		List<Criterion> criterionList = new ArrayList<Criterion>();
		for (PropertyFilter filter : filters) {

			if (!filter.isMultiProperty()) { // properNameName中只有一个属性的情况.
				Criterion criterion = buildPropertyCriterion(filter
						.getPropertyName(), filter.getValue(), filter
						.getMatchType());
				criterionList.add(criterion);
			} else {// properName中包含多个属性的情况,进行or处理.
				Disjunction disjunction = Restrictions.disjunction();

				for (String param : filter.getPropertyNames()) {
					Criterion criterion = buildPropertyCriterion(param, filter
							.getValue(), filter.getMatchType());
					disjunction.add(criterion);
				}
				criterionList.add(disjunction);
			}
		}
		return criterionList.toArray(new Criterion[criterionList.size()]);
	}

	/**
	 * 获取别名列表
	 * 
	 * @param filters
	 * @return
	 */
	protected List<String> getAliasList(final List<PropertyFilter> filters) {
		List<String> aliasList = new ArrayList<String>();

		for (PropertyFilter filter : filters) {

			if (!filter.isMultiProperty()) {
				String alias = getAlias(filter.getPropertyName());

				if (StringUtil.notEmpty(alias) && !aliasList.contains(alias)) {
					aliasList.add(alias);
				}
			} else {// properName中包含多个属性的情况,进行or处理.
				for (String param : filter.getPropertyNames()) {
					String alias = getAlias(param);
					if (StringUtil.notEmpty(alias)
							&& !aliasList.contains(alias)) {
						aliasList.add(alias);
					}
				}
			}
		}

		return aliasList;
	}

	/**
	 * 获取别名
	 * 
	 * @param str
	 * @return
	 */
	private String getAlias(String str) {
		String alias = "";
		int pos = str.indexOf(".");
		if (pos >= 0) {
			alias = str.substring(0, pos);
			if (str.substring(pos + 1, str.length()).equals("id")) {
				alias = "";
			}
		}
		return alias;
	}

	/**
	 * search by sql
	 * 
	 * @param values
	 *            valiable parameter
	 */
	public List findEntityBySql(String sql, Map<String, Class> returnEntities) {
		SQLQuery sqlQuery = getSession().createSQLQuery(sql);
		Set<String> keySet = returnEntities.keySet();
		Iterator<String> iterator = keySet.iterator();
		while (iterator.hasNext()) {
			String strKey = iterator.next();
			sqlQuery.addEntity(strKey, returnEntities.get(strKey));
		}
		return sqlQuery.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP)
				.list();
	}

	public List findScalarBySql(String sql, List<String> returnEntities) {
		SQLQuery sqlQuery = getSession().createSQLQuery(sql);
		Iterator<String> iterator = returnEntities.iterator();
		while (iterator.hasNext()) {
			String strKey = iterator.next();
			sqlQuery.addScalar(strKey);
		}
		return sqlQuery.list();
	}

	public void executeBySql(String sql) {
		SQLQuery sqlQuery = getSession().createSQLQuery(sql);
		sqlQuery.executeUpdate();
	}

	/**
	 * remove by hql
	 * 
	 * @param values
	 *            valiable parameter
	 */
	public void remove(String hql, Object... values) {
		Assert.hasText(hql);
		Query query = getSession().createQuery(hql);
		for (int i = 0; i < values.length; i++) {
			query.setParameter(i, values[i]);
		}
		query.executeUpdate();
	}

	/**
	 * update by hql
	 * 
	 * @param values
	 *            valiable parameter
	 */
	public void update(String hql, Object... values) {
		Assert.hasText(hql);
		Query query = getSession().createQuery(hql);
		for (int i = 0; i < values.length; i++) {
			query.setParameter(i, values[i]);
		}
		query.executeUpdate();
	}

	/**
	 * Get object by id
	 */
	public Object getObject(Class clazz, Serializable id) {
		try {
			return getHibernateTemplate().get(clazz, id);
		} catch (IllegalArgumentException e) {
			return null;
		}
	}

	/**
	 * Get unique Model
	 */
	public Object getObject(Class clazz, String columnName, String clumnValue) {
		Object o = getSession().createCriteria(clazz).add(
				Restrictions.like(columnName, clumnValue)).uniqueResult();
		if (o == null) {
			throw new ObjectRetrievalFailureException(clazz, columnName);
		}
		return o;
	}

	/**
	 * Get objects
	 */
	public List getObjects(Class clazz) {
		return getHibernateTemplate().loadAll(clazz);
	}

	/**
	 * Only Save Model
	 */
	public void saveObject(Object o) {
		getHibernateTemplate().save(o);
		getHibernateTemplate().flush();
	}

	/**
	 * Save Or Update Model
	 */
	public void saveOrUpdateObject(Object o) {
		getHibernateTemplate().saveOrUpdate(o);
		getHibernateTemplate().flush();
	}

	/**
	 * Merge Model
	 */
	public Object merge(Object o) {
		Object t = getHibernateTemplate().merge(o);
		getHibernateTemplate().flush();
		return t;
	}

	/**
	 * Only update Model
	 */
	public void updateObject(Object o) {
		getHibernateTemplate().update(o);
		getHibernateTemplate().flush();
	}

	/**
	 * Remove object by id
	 */
	public void removeObject(Class clazz, Serializable id) {
		getHibernateTemplate().delete(getObject(clazz, id));
		this.flush();
	}

	public void removeObject(Object obj) {
		getHibernateTemplate().delete(obj);
		this.flush();
	}

	// ////////////////// Look up object//////////////////////////////////
	/**
	 * Get Model by object's specific parameter Example: dao.createQuery(hql)
	 * dao.createQuery(hql,arg0); dao.createQuery(hql,arg0,arg1);
	 * dao.createQuery(hql,new Model[arg0,arg1,arg2])
	 * 
	 * @param parameters
	 *            valiable parameter
	 */
	public Object getObject(Object object, String... parameters) {
		Criteria criteria = getSession().createCriteria(object.getClass());
		for (int i = 0; i < parameters.length; i++) {
			criteria.add(Restrictions.eq(parameters[i], BeanUtil
					.getPropertyValue(object, parameters[i])));
		}
		return criteria.uniqueResult();
	}

	/**
	 * Get Objects by object's specific parameter
	 * 
	 * @param parameters
	 *            valiable parameter
	 */
	public List getObjects(Object object, String... parameters) {
		Criteria criteria = getSession().createCriteria(object.getClass());
		for (int i = 0; i < parameters.length; i++) {
			criteria.add(Restrictions.eq(parameters[i], BeanUtil
					.getPropertyValue(object, parameters[i])));
		}
		return criteria.list();
	}

	/**
	 * Get results by criterias
	 * 
	 * @param clazz
	 *            Class
	 * @param criterias
	 *            Map
	 * @return
	 */
	public List getObjects(Class clazz, Map criterias) {
		Criteria criteria = getSession().createCriteria(clazz);
		for (Iterator iter = criterias.keySet().iterator(); iter.hasNext();) {
			String key = (String) iter.next();
			criteria.add(Restrictions.eq(key, criterias.get(key)));
		}
		return criteria.list();
	}

	// ==========================================================================================

	/**
	 * 按属性查找唯一对象,匹配方式为相等.
	 */
	public Object findUnique(final Class entityClass,
			final String propertyName, final Object value) {
		Assert.hasText(propertyName, "propertyName must not be null");
		Criterion criterion = Restrictions.eq(propertyName, value);
		return createCriteria(entityClass, criterion).uniqueResult();
	}

	/**
	 * 按属性查找唯一对象,匹配方式为相等.
	 */
	public Object findUnique(final Class entityClass,
			final String[] propertyNames, final Object[] values) {
		List<Criterion> criterionList = new ArrayList<Criterion>();
		for (int i = 0; i < propertyNames.length; i++) {
			Criterion criterion = Restrictions.eq(propertyNames[i], values[i]);
			criterionList.add(criterion);
		}

		Criterion[] criterions = criterionList
				.toArray(new Criterion[criterionList.size()]);

		return createCriteria(entityClass, criterions).uniqueResult();
	}

	/**
	 * 按属性查找对象列表,匹配方式为相等.
	 */
	public List find(final Class entityClass, final String propertyName,
			final Object value) {
		Assert.hasText(propertyName, "propertyName must not be null");

		Criterion criterion = Restrictions.eq(propertyName, value);
		return find(entityClass, criterion);
	}

	/**
	 * 按 单个字段进行查询，并按 orderBy 进行排序
	 * 
	 * @param entityClass
	 * @param orderBy
	 * @param isAsc
	 * @param propertyName
	 * @param value
	 * @return
	 */
	public List find(final Class entityClass, String orderBy, boolean isAsc,
			final String propertyName, final Object value) {
		Assert.hasText(propertyName, "propertyName must not be null");

		Criterion criterion = Restrictions.eq(propertyName, value);
		return createCriteria(entityClass, orderBy, isAsc, criterion).list();
	}

	/**
	 * 按属性查找对象列表,匹配方式为相等.
	 * 
	 * @param propertyName
	 * @param value
	 * @return
	 */
	public List find(final Class entityClass, final String[] propertyName,
			final Object[] value) {
		// Assert.isTrue(propertyName.length!=value.length, "length isn't
		// match");

		List<Criterion> criterionList = new ArrayList<Criterion>();
		for (int i = 0; i < propertyName.length; i++) {
			Criterion criterion = Restrictions.eq(propertyName[i], value[i]);
			criterionList.add(criterion);
		}

		Criterion[] cri = criterionList.toArray(new Criterion[criterionList
				.size()]);

		return find(entityClass, cri);
	}

	/**
	 * 按多个 属性进行查询，并进行排序
	 * 
	 * @param entityClass
	 * @param orderBy
	 * @param isAsc
	 * @param propertyName
	 * @param value
	 * @return
	 */
	public List find(final Class entityClass, String orderBy, boolean isAsc,
			final String[] propertyName, final Object[] value) {
		List<Criterion> criterionList = new ArrayList<Criterion>();
		for (int i = 0; i < propertyName.length; i++) {
			Criterion criterion = Restrictions.eq(propertyName[i], value[i]);
			criterionList.add(criterion);
		}

		Criterion[] criterions = criterionList
				.toArray(new Criterion[criterionList.size()]);

		return createCriteria(entityClass, orderBy, isAsc, criterions).list();
	}

	/**
	 * 按Criteria查询对象列表.
	 * 
	 * @param criterions
	 *            数量可变的Criterion.
	 */
	public List find(final Class entityClass, final Criterion... criterions) {
		return createCriteria(entityClass, criterions).list();
	}

	/**
	 * 按属性条件参数创建Criterion,辅助函数. in查询，使用带逗号{,}的字符串。
	 */
	protected Criterion buildPropertyCriterion(final String propertyName,
			final Object value, final MatchType matchType) {
		Assert.hasText(propertyName, "propertyName不能为空");
		Criterion criterion = null;

		if (MatchType.EQ.equals(matchType)) {
			if (value == null) {
				criterion = Restrictions.isNull(propertyName);
			} else {
				criterion = Restrictions.eq(propertyName, value);
			}
		} else if (MatchType.LIKE.equals(matchType)) {
			criterion = Restrictions.like(propertyName, (String) value,
					MatchMode.ANYWHERE);
		}

		else if (MatchType.LIKER.equals(matchType)) {
			criterion = Restrictions.like(propertyName, (String) value,
					MatchMode.START);
		}

		// great than
		else if (MatchType.GT.equals(matchType)) {
			criterion = Restrictions.gt(propertyName, value);
		}
		// little than
		else if (MatchType.LT.equals(matchType)) {
			criterion = Restrictions.lt(propertyName, value);
		}

		// great equal
		else if (MatchType.GE.equals(matchType)) {
			criterion = Restrictions.ge(propertyName, value);
		}

		// little less and equal than
		else if (MatchType.LE.equals(matchType)) {
			criterion = Restrictions.le(propertyName, value);
		}

		// not equal
		else if (MatchType.NE.equals(matchType)) {
			criterion = Restrictions.ne(propertyName, value);
		}

		// in
		else if (MatchType.IN.equals(matchType)) {
			Object[] values = value.toString().split(",");
			criterion = Restrictions.in(propertyName, values);
		}

		return criterion;
	}
}
